#include "base/core/os/os_head.h"
#include "DBOption/Mysql/MysqlConnector.h"
#include "base/core/com_define.h"
#include "base/core/os/string.h"
#include "base/core/os/debug.h"
#include "base/core/os/time.h"
#include "base/core/os/mutex.h"
#include "base/core/char_operation.h"

namespace 
{
	static MutexLock g_sqlInitLock;
}

MysqlConnector::MysqlConnector()
	: m_port(0)
	,m_mysqlEnv(nullptr)
{
	memset(m_ip, 0, sizeof(m_ip));
	memset(m_user, 0, sizeof(m_user));
	memset(m_password, 0, sizeof(m_password));
	memset(m_database, 0, sizeof(m_database));
}

MysqlConnector::~MysqlConnector() 
{
    Close();
}

bool MysqlConnector::Connect(
							const char* ip,
							uint32_t    port,
							const char* user,
							const char* password,
							const char* database,
							const char* characterSet
							) 
{
    bool result = false;
    bool retCode = false;

    MMO_ASSERT(database);
    MMO_ASSERT(user);
    MMO_ASSERT(password);
    MMO_ASSERT(ip);

    m_port = port;

    retCode = SafeCopyString(m_ip, sizeof(m_ip), ip);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = SafeCopyString(m_user, sizeof(m_user), user);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = SafeCopyString(m_password, sizeof(m_password), password);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = SafeCopyString(m_database, sizeof(m_database), database);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = SafeCopyString(m_characterSet, sizeof(m_characterSet), characterSet);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = _Connect();
    MMOLOG_PROCESS_ERROR(retCode);

    result = true;
Exit0:
    return result;
}

bool MysqlConnector::Close() 
{
    if (m_mysqlEnv != nullptr) 
	{
        mysql_close(m_mysqlEnv);
        m_mysqlEnv = nullptr;
    }
    return true;
}

bool MysqlConnector::Execute(const char* query, uint32_t length) 
{
    bool result = false;
    int  retCode = 0;

    MMOLOG_ASSERT_EXIT(m_mysqlEnv != nullptr);
    MMOLOG_ASSERT_EXIT(query != nullptr);
    MMOLOG_ASSERT_EXIT(length > 0);

    retCode = mysql_real_query(m_mysqlEnv, query, length);
    if (retCode != 0) 
	{
        int error = mysql_errno(m_mysqlEnv);
        if (error == CR_SERVER_GONE_ERROR ||
            error == CR_SERVER_LOST ||
            error == CR_CONN_HOST_ERROR) 
		{
            LogErrFmtPrint("[common] Connection lost, try reconnect mysql database...");
            if (_Connect()) 
			{
                retCode = mysql_real_query(m_mysqlEnv, query, length);
            }
        }
    }

    if (retCode != 0) 
	{
        char buffer[1024];
        uint32_t len = TMIN<uint32_t>(sizeof(buffer) - 1, length);
        memcpy(buffer, query, len);
        buffer[len] = '\0';
        LogErrFmtPrint("[common] Failed MySQL Query: %s", buffer);
        if (m_mysqlEnv != nullptr) 
		{
            LogErrFmtPrint("[common] mysql error:%s", mysql_error(m_mysqlEnv));
        }
        goto Exit0;
    }
    result = true;
Exit0:
    return result;
}

bool MysqlConnector::EscapeString(
								  char* dst,
								  const char* src,
								  uint32_t srcLength,
								  uint32_t& escapeLength
								 ) 
{
    MMO_ASSERT(dst);
    MMO_ASSERT(src);
    bool result = false;

    MMOLOG_ASSERT_EXIT(m_mysqlEnv != nullptr);
    escapeLength = mysql_real_escape_string(m_mysqlEnv, dst, src, srcLength);
    result = true;
Exit0:
    return result;
}

bool MysqlConnector::_Connect() 
{
    static const int MAX_RETRY_TIME = 1000;
    bool result   = false;
    int  retCode  = 0;
    int  tryTimes = 0;

    Close();

    g_sqlInitLock.Lock();
    m_mysqlEnv = mysql_init(nullptr);
    g_sqlInitLock.Unlock();

    MMOLOG_ASSERT_EXIT(m_mysqlEnv != nullptr);
    do 
	{
        MYSQL* connectRet = mysql_real_connect(
												m_mysqlEnv,
												m_ip,
												m_user,
												m_password,
												m_database,
												m_port,
												nullptr,
												CLIENT_MULTI_STATEMENTS | CLIENT_IGNORE_SIGPIPE
											  );

        if (connectRet)
            break;

        if (tryTimes++ % 50 == 0) 
		{
            LogErrFmtPrint("[common] connect mysql server %s:%d(db=%s) fail err:%s, try reconnect",
                m_ip, m_port, m_database, mysql_error(m_mysqlEnv));
        }

        Time::Sleep(500);
    } while (tryTimes < MAX_RETRY_TIME);

    MMOLOG_PROCESS_ERROR(tryTimes < MAX_RETRY_TIME);

    LogInfoFmtPrint("[common] connect mysql server %s:%d(db=%s) success", m_ip, m_port, m_database);

    retCode = mysql_set_character_set(m_mysqlEnv, m_characterSet);
    if (retCode != 0) 
	{
        LogErrFmtPrint("[common] set mysql charset %s fail.", m_characterSet);
        goto Exit0;
    }
    result = true;
Exit0:
    if (!result) 
	{
        LogErrFmtPrint("[common] connect mysql server %s:%d(db=%s) fail err:%s", m_ip, m_port, m_database, mysql_error(m_mysqlEnv));
        Close();
    }
    return result;
}

MYSQL_RES* MysqlConnector::GetQueryResult() 
{
    MYSQL_RES* res = nullptr;
    MMOLOG_ASSERT_EXIT(m_mysqlEnv != nullptr);
    res = mysql_store_result(m_mysqlEnv);
    if (res == nullptr) 
	{
        LogErrFmtPrint("[common] MysqlConnector::GetQueryResult fail: %s", mysql_error(m_mysqlEnv));
    }
Exit0:
    return res;
}

int MysqlConnector::GetAffectedRows() 
{
    int result = -1;
    MMOLOG_PROCESS_ERROR(m_mysqlEnv != nullptr);
    result = (int)mysql_affected_rows(m_mysqlEnv);
Exit0:
    return result;
}

bool MysqlConnector::IgnoreLastDummyResult() 
{
    bool result = false;
    MYSQL_RES* mysqlResult = nullptr;
    // mysql doc:20.6.16 C API Support for Multiple Statement Execution
    // https://dev.mysql.com/doc/refman/5.0/en/c-api-multiple-queries.html
    int state = mysql_next_result(m_mysqlEnv);
    MMO_PROCESS_SUCCESS(state != 0);
    mysqlResult = mysql_store_result(m_mysqlEnv); // the ignore one must be dummy
    MMOLOG_ASSERT_EXIT(mysqlResult == nullptr); // just a result statue, no result data.

Exit1:
    result = true;
Exit0:
    return result;
}
